﻿using System;
using System.Runtime.Remoting.Contexts;
using ActProgTypeLib;

namespace MDK.Device
{
    [Synchronization]
    public class MitsubishiPlcByAct : ContextBoundObject, IPlc
    {
        public string IP { get; set; }
        public int ActUnitType { get; set; }
        public int ActProtocolType { get; set; }
        public int ActCpuType { get; set; }

        private ActProgTypeClass _actProg;
        
        public bool IsOpened { get; private set; }

        public void Open()
        {
            Close();

            _actProg = new ActProgTypeClass
            {
                ActCpuType = ActCpuType,
                ActDestinationPortNumber = 5560,
                ActTimeOut = 5000,
                ActUnitType = ActUnitType,
                ActProtocolType = ActProtocolType,
                ActHostAddress = IP //设置IP 
            };
            var retryCount = 0;
            int errorcode = 0;
            do
            {
                var errorCode = _actProg.Open();
                if (errorCode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
            IsOpened = errorcode == 0;
        }

        public bool BitIsSet(PlcAddress addr)
        {
            var retryCount = 0;
            int value;
            do
            {
                var errorcode = _actProg.ReadDeviceRandom(addr, 1, out value);
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);

            return value != 0;
        }

        public void SetBit(PlcAddress addr)
        {
            var retryCount = 0;
            do
            {
                var errorcode = _actProg.WriteDeviceRandom(addr, 1, 1);
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
        }

        public void ResetBit(PlcAddress addr)
        {
            var retryCount = 0;
            do
            {
                var errorcode = _actProg.WriteDeviceRandom(addr, 1, 0);
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
        }

        public int ReadInt(PlcAddress addr)
        {
            int value = Int32.MinValue;
            var retryCount = 0;
            do
            {
                int errorcode = -1;
                if (addr.DataType == PlcAddress.PLC_DATATYPE_DINT32)
                {
                    int[] temp = new int[2];
                    errorcode = _actProg.ReadDeviceBlock(addr, 1, out temp[0]);
                    if (errorcode == 0)
                    {
                        errorcode = _actProg.ReadDeviceBlock("D" + (addr.Address + 1), 1, out temp[1]);
                        if (errorcode == 0)
                        {
                            value = (temp[1] << 16) + temp[0];
                        }
                    }
                }
                else
                {
                    errorcode = _actProg.ReadDeviceBlock(addr, 1, out value);
                }
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
            

            return value;
        }

        public void WriteInt(PlcAddress addr, int value)
        {
            var retryCount = 0;
            do
            {
                int errorcode = -1;
                if (addr.DataType == PlcAddress.PLC_DATATYPE_DINT32)
                {
                    int[] temp = new int[2];
                    temp[1] = ((value >> 16) & 0xFFFF);
                    temp[0] = (value & 0xFFFF);
                    errorcode = _actProg.WriteDeviceBlock(addr, 1, ref temp[0]);
                    if (errorcode == 0)
                    {
                        errorcode = _actProg.WriteDeviceBlock("D" + (addr.Address + 1), 1, ref temp[1]);
                    }
                }
                else
                {
                    _actProg.WriteDeviceBlock(addr, 1, ref value);
                }
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
        }

        public void Close()
        {
            _actProg?.Close();
            _actProg = null;
        }

        public event CommunicationErrorHandler CommunicationError;

        protected virtual bool OnCommunicationError(int errorcode, int retryCount)
        {
            var handler = CommunicationError;
            if (handler != null)
            {
                return handler.Invoke(this, ErrorCodeHelper.PlcErrorCodeBase + errorcode, retryCount);
            }

            return true;
        }
    }
}